#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>
#include "structs.h"

int kNumAlunos = 0; //total de alunos
int kNumAulas = 0; //total de aulas
float kMediaProvas = 0; //media geral das provas
float kMediaTrabalhos = 0; //media geral dos trabalhos
char* kTabelaAlunosMatricula = "alunos_matricula.txt"; //arquivo com a tabela de nomes e matriculas
char* kTabelaMatriculaFaltas = "matricula_faltas.txt"; //arquivo co a tabela de matriculas e faltas
char* kTabelaMatriculaProvas = "matricula_provas.txt"; //arquivo co a tabela de matriculas e notas das provas 1 e 2
char* kTabelaNomeTrabalhos = "nome_trabalhos.txt"; //arquivo co a tabela de nomes e notas dos trabalhos 1, 2 e 3

Lista cria_lista_vazia(){
  /* inicializa os elementos da lista */
  Lista lista;
  lista.primeiro = NULL;
  lista.ultimo = NULL;
  lista.tamanho = 0;

  return lista;
}

float calcula_media(Aluno* aluno){
  //Media = (N1 + (N2*8+(((T1+T2+T3)*2)/10))/2
  return (aluno->notas[0]+
      (aluno->notas[1]*8+
      (aluno->notas[2]+aluno->notas[3]+aluno->notas[4])*2)
      /10)
      /2;
}

void swap(Lista *lista,Aluno* primeiro, Aluno* segundo) {
  /* Troca o primeiro elemento de posicao com o segundo */
  if (primeiro == lista->primeiro) {
    lista->primeiro = segundo;
  }
  if (segundo == lista->ultimo) {
      lista->ultimo = primeiro;
  }
  Aluno *temp = primeiro->proximo;
  primeiro->proximo = segundo->proximo;
  segundo->proximo = temp;
  if (primeiro->proximo != NULL) {
      primeiro->proximo->anterior = primeiro;
  }
  if (segundo->proximo != NULL) {
      segundo->proximo->anterior =segundo;
  }
  temp = primeiro->anterior;
  primeiro->anterior = segundo->anterior;
  segundo->anterior = temp;
  if (primeiro->anterior != NULL) {
      primeiro->anterior->proximo = primeiro;
  }
  if (segundo->anterior == NULL) {
      return;
  }
  segundo->anterior->proximo = segundo;
}

void bubble_sort(Lista* lista) {
  /* bubble sort com flag para lista duplamente encadeada */
  for (Aluno* i = lista->primeiro; i != NULL; i = i->proximo) {
    bool flag = false;
    for (Aluno* j = lista->primeiro; j != NULL; j = j->proximo) {
      if (j->proximo != NULL) {
        if (j->faltas > j->proximo->faltas) {
          swap(lista,j, j->proximo);
          flag = true;
        }
      }
    }
    if (!flag)
        return;
  }
}

Aluno* pesquisa_nome(Lista *lista,char* nome ){
 /* Pesquisa na lista por um nome e retorna um ponteiro para aquele elemento
  * retorna NULL caso nao encontre aquele nome
  */
  Aluno *elemento_esq = lista->primeiro,*elemento_dir = lista->ultimo;
  while(elemento_esq != elemento_dir && elemento_esq->anterior != elemento_dir){
    if(strcmp(elemento_esq->nome,nome) == 0)
      return elemento_esq;
    else if(strcmp(elemento_dir->nome,nome) == 0)
      return elemento_dir;
    else{
      elemento_esq = elemento_esq->proximo;
      elemento_dir = elemento_dir->anterior;
    }
  }
  return NULL;
}

Aluno* pesquisa_matricula(Lista *lista,int matricula ){
 /* Pesquisa na lista por um nome e retorna um ponteiro para aquele elemento
  * retorna NULL caso nao encontre aquele nome
  */
  Aluno *elemento_esq = lista->primeiro,*elemento_dir = lista->ultimo;
  while(elemento_esq != elemento_dir && elemento_esq->anterior != elemento_dir){
    if(elemento_esq->matricula == matricula)
      return elemento_esq;
    else if(elemento_dir->matricula == matricula)
      return elemento_dir;
    else{
      elemento_esq = elemento_esq->proximo;
      elemento_dir = elemento_dir->anterior;
    }
  }
  return NULL;
}

Lista retorna_aprovados(Lista* lista){
  /* retorna uma lista com todos os alunos aprovados */
  Lista ret = cria_lista_vazia();
  Aluno *elemento_atual = lista->primeiro;
  while(elemento_atual != NULL){
    if(elemento_atual->aprovado == true){
      Aluno *novo_elemento = (Aluno*) malloc(sizeof(Aluno));
      *(novo_elemento) = *(elemento_atual);
      if(ret.tamanho == 0){
        novo_elemento->anterior = ret.primeiro; //null
        novo_elemento->proximo = ret.ultimo; //null
        ret.primeiro = novo_elemento;
        ret.ultimo = novo_elemento;
      }
      else{
        novo_elemento->anterior = ret.ultimo;
        novo_elemento->proximo = NULL;
        ret.ultimo->proximo = novo_elemento;
        ret.ultimo = novo_elemento;
      }
      ret.tamanho++;
    }
    elemento_atual = elemento_atual->proximo;
  }
  return ret;
}

Lista retorna_reprovados(Lista* lista){
  /* retorna uma lista com todos os alunos reprovados */
  Lista ret = cria_lista_vazia();
  Aluno *elemento_atual = lista->primeiro;
  while(elemento_atual!= NULL){
    if(elemento_atual->aprovado == false
       && elemento_atual->prova_final == false){
      Aluno *novo_elemento = (Aluno*) malloc(sizeof(Aluno));
      *(novo_elemento) = *(elemento_atual);
      if(ret.tamanho == 0){
        novo_elemento->anterior = ret.primeiro; //null
        novo_elemento->proximo = ret.ultimo; //null
        ret.primeiro = novo_elemento;
        ret.ultimo = novo_elemento;
      }
      else{
        novo_elemento->anterior = ret.ultimo;
        novo_elemento->proximo = NULL;
        ret.ultimo->proximo = novo_elemento;
        ret.ultimo = novo_elemento;
      }
      ret.tamanho++;
    }
    elemento_atual = elemento_atual->proximo;
  }
  return ret;
}

Lista retorna_af(Lista* lista){
  /* retorna uma lista com todos os alunos em AF */
  Lista ret = cria_lista_vazia();
  Aluno *elemento_atual = lista->primeiro;
  while(elemento_atual != NULL){
    if(elemento_atual->prova_final == true){
      Aluno *novo_elemento = (Aluno*) malloc(sizeof(Aluno));
      *(novo_elemento) = *(elemento_atual);
      if(ret.tamanho == 0){
        novo_elemento->anterior = ret.primeiro; //null
        novo_elemento->proximo = ret.ultimo; //null
        ret.primeiro = novo_elemento;
        ret.ultimo = novo_elemento;
      }
      else{
        novo_elemento->anterior = ret.ultimo;
        novo_elemento->proximo = NULL;
        ret.ultimo->proximo = novo_elemento;
        ret.ultimo = novo_elemento;
      }
      ret.tamanho++;
    }
    elemento_atual = elemento_atual->proximo;
  }
  return ret;
}

void imprime_lista(Lista lista){
 /* imprime os alunos de uma lista */
  using namespace std;
  Aluno* elemento_atual = lista.primeiro;
  while(elemento_atual != NULL){
    cout << calcula_media(elemento_atual) << " - "<< elemento_atual->matricula << " - " << elemento_atual->nome;
    if(elemento_atual->prova_final)
      cout << " - " << elemento_atual->nota_final;
    cout << endl;
    elemento_atual = elemento_atual->proximo;
  }
}

void output(Lista* lista_alunos){
  /* devolve a saida esperada do programa segundo as especficacoes do trabalho */
  using namespace std;
  cout << "Media das notas das avaliacoes: " << kMediaProvas << endl;
  cout << "Media das notas das trabalhos: " << kMediaTrabalhos << endl;
  cout << endl;
  Lista lista_aprovados = retorna_aprovados(lista_alunos),
        lista_reprovados = retorna_reprovados(lista_alunos),
        lista_af = retorna_af(lista_alunos);

  bubble_sort(lista_alunos);
  Aluno *elemento_atual = lista_alunos->primeiro;
  while(elemento_atual != NULL){
    cout << elemento_atual->faltas << " - " << elemento_atual->nome;
    if(elemento_atual->prova_final == true)
      cout << " [AF]";
    else if (elemento_atual->aprovado == false)
      cout << " [Reprovado]";
    cout << endl;
    elemento_atual = elemento_atual->proximo;
  }
  cout << "\n[Aprovados]" << endl;
  imprime_lista(lista_aprovados);
  cout << "\n[Reprovados]" << endl;
  imprime_lista(lista_reprovados);
  cout << "\n[AF]" << endl;
  imprime_lista(lista_af);
}

void adiciona_elemento(Lista* lista,char* nome,int matricula){
  Aluno* novo_elemento;
  novo_elemento = (Aluno*) malloc(sizeof(Aluno));
  novo_elemento->aprovado = false;
  novo_elemento->prova_final = false;
  novo_elemento->matricula = matricula;
  strcpy(novo_elemento->nome,nome);

  if(lista->tamanho == 0){
    novo_elemento->anterior = lista->primeiro; //null
    novo_elemento->proximo = lista->ultimo; //null
    lista->primeiro = novo_elemento;
    lista->ultimo = novo_elemento;
  }
  else{
    novo_elemento->anterior = lista->ultimo;
    novo_elemento->proximo = NULL;
    lista->ultimo->proximo = novo_elemento;
    lista->ultimo = novo_elemento;
  }
  lista->tamanho++;
}

void le_arquivos(Lista* lista){
 /* Le as quatro tabelas e guarda os dados numa lista de alunos */
  using namespace std;
  ifstream tabela(kTabelaAlunosMatricula);

  while(tabela.good())
  { /* Criando os elementos da lista */
    if (kNumAlunos == 0){
      /* A primeira linha contem o num. total de alunos*/
      tabela >> kNumAlunos;
    }
    char nome[30];
    int mat;
    tabela >> mat;
    tabela >> nome;
    adiciona_elemento(lista,nome,mat);
    if(lista->tamanho == kNumAlunos)
      break;
  }

  tabela.close();
  /* A partir daqui a lista ja tem todos os alunos,
   * vamos apenas adicionar os dados correspondentes
   * as outras tabelas
   */
  tabela.open(kTabelaMatriculaProvas);
  while(tabela.good())
  {
    int matricula_atual;
    tabela >> matricula_atual;
    Aluno* elemento_atual = (Aluno*) malloc(sizeof(Aluno));
    elemento_atual= pesquisa_matricula(lista,matricula_atual);
    if(elemento_atual != NULL){
      tabela >> elemento_atual->notas[0]; // nota prova 1
      tabela >> elemento_atual->notas[1]; // nota prova 2
      kMediaProvas += ((elemento_atual->notas[0]+elemento_atual->notas[1])/2)/kNumAlunos;
    }
  }
  tabela.close();
  tabela.open(kTabelaNomeTrabalhos);
  while(tabela.good())
  {
    char nome_atual[30];
    tabela >> nome_atual;
    Aluno* elemento_atual = (Aluno*) malloc(sizeof(Aluno));
    elemento_atual= pesquisa_nome(lista,nome_atual);
    if(elemento_atual != NULL){
      tabela >> elemento_atual->notas[2]; // nota trab 1
      tabela >> elemento_atual->notas[3]; //nota trab 2
      tabela >> elemento_atual->notas[4]; //nota trab 3
      kMediaTrabalhos += ((elemento_atual->notas[2]+elemento_atual->notas[3]+elemento_atual->notas[4])/3)/kNumAlunos;
      float media = calcula_media(elemento_atual);
      if(media >= 7){
        elemento_atual->aprovado = true;
        elemento_atual->prova_final = false;
        elemento_atual->nota_final = 0;
      }
      else{
        elemento_atual->aprovado = false;
        elemento_atual->nota_final = 10 - media;
        if(elemento_atual->nota_final > 7)
          elemento_atual->prova_final = false;
        else
          elemento_atual->prova_final = true;
      }
    }
  }
  tabela.close();
  tabela.open(kTabelaMatriculaFaltas);
  while(tabela.good())
  {
    if (kNumAulas == 0){
      tabela >> kNumAulas;
    }
    else{
      int matricula_atual,faltas_e1,faltas_e2;
      tabela >> matricula_atual;
      tabela >> faltas_e1; //faltas etapa 1
      tabela >> faltas_e2; //faltas etapas 2
      Aluno* elemento_atual = (Aluno*) malloc(sizeof(Aluno));
      elemento_atual= pesquisa_matricula(lista,matricula_atual);
      if(elemento_atual != NULL){
        elemento_atual->faltas = faltas_e1 + faltas_e2;
        if( (float)(elemento_atual->faltas) > 0.3*kNumAulas){
          elemento_atual->aprovado = false;
        }
      }
    }
  }
  tabela.close();
}
